package com.pig4cloud.pigx.wallet.configuration;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.protobuf.ByteString;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.user.entity.User;
import com.pig4cloud.pigx.wallet.api.feign.RemoteRippleUserService;
import com.pig4cloud.pigx.wallet.constant.UserFundRechargeTypeEnum;
import com.pig4cloud.pigx.wallet.constant.WalletConstant;
import com.pig4cloud.pigx.wallet.constant.WalletStatusEnum;
import com.pig4cloud.pigx.wallet.dto.ChargeMentionNotificationDTO;
import com.pig4cloud.pigx.wallet.dto.TronContractsEventsTransfer;
import com.pig4cloud.pigx.wallet.entity.Account;
import com.pig4cloud.pigx.wallet.entity.Currency;
import com.pig4cloud.pigx.wallet.entity.CurrentBlock;
import com.pig4cloud.pigx.wallet.entity.Notification;
import com.pig4cloud.pigx.wallet.entity.RechargeRecord;
import com.pig4cloud.pigx.wallet.mapstruct.NotificationStruct;
import com.pig4cloud.pigx.wallet.service.AccountService;
import com.pig4cloud.pigx.wallet.service.CurrencyService;
import com.pig4cloud.pigx.wallet.service.CurrentBlockService;
import com.pig4cloud.pigx.wallet.service.NotificationService;
import com.pig4cloud.pigx.wallet.service.RechargeRecordService;
import io.reactivex.Flowable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.tron.trident.core.ApiWrapper;
import org.tron.trident.utils.Base58Check;
import org.web3j.abi.EventEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthBlockNumber;
import org.web3j.protocol.core.methods.response.EthLog;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Component
@RequiredArgsConstructor
public class CommandLineRunnerImpl implements CommandLineRunner {

    private final Web3j web3j;

    private final RabbitTemplate rabbitTemplate;

    private final CurrentBlockService currentBlockService;

    private final AccountService accountService;

    private final RechargeRecordService rechargeRecordService;

    private final CurrencyService currencyService;

    private final OkHttpClient okHttpClient;

    /**
     * 用户消息通知
     */
    private final NotificationService notificationService;

    private final RemoteRippleUserService remoteRippleUserService;

    @Resource(name = "ethThreadPoolExecutor")
    private ThreadPoolExecutor ethThreadPoolExecutor;

    @Resource(name = "tronThreadPoolExecutor")
    private ThreadPoolExecutor tronThreadPoolExecutor;

    private final AES aes;


    @Override
    public void run(String... args) {
        // ethThreadPoolExecutor.execute(this::eth);
        // tronThreadPoolExecutor.execute(this::tron);
    }

    public void eth() {

        CurrentBlock currentBlock = currentBlockService.getOne(new LambdaQueryWrapper<CurrentBlock>()
                .eq(CurrentBlock::getPublicChain, WalletConstant.ETH)
        );
        if (currentBlock == null) {
            org.web3j.protocol.core.Request<?, EthBlockNumber> ethBlockNumberRequest = web3j.ethBlockNumber();
            EthBlockNumber send;
            try {
                send = ethBlockNumberRequest.send();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            BigInteger blockNumber = send.getBlockNumber();
            currentBlock = new CurrentBlock();
            currentBlock.setPublicChain(WalletConstant.ETH);
            currentBlock.setBlockNumber(String.valueOf(blockNumber));
            currentBlockService.save(currentBlock);
        }

        Currency currencyServiceOne = currencyService.getOne(new LambdaQueryWrapper<Currency>()
                .eq(Currency::getPublicChain, WalletConstant.ETH)
                .eq(Currency::getName, WalletConstant.USDT)
                .eq(Currency::getState, WalletConstant.TRUE)
        );

        final String[] blockNumber = {currentBlock.getBlockNumber()};

        Flowable<EthBlock> ethBlockFlowable = web3j.replayPastAndFutureBlocksFlowable(
                DefaultBlockParameter.valueOf(BigInteger.valueOf(Long.parseLong(blockNumber[0]))),
                false);

        CurrentBlock currentBlockUpdate = new CurrentBlock();
        currentBlockUpdate.setId(currentBlock.getId());

        BigDecimal wei = BigDecimal.valueOf(Math.pow(10, Double.parseDouble(currencyServiceOne.getAccuracy())));

        ethBlockFlowable.blockingSubscribe(ethBlock -> {
//			log.info("----当前区块：{}--", JSONUtil.parseObj(ethBlock.getBlock()));
            EthFilter filter = new EthFilter(ethBlock.getBlock().getHash(), currencyServiceOne.getSmartContractAddress());
            Event event = new Event("Transfer",
                    Arrays.<TypeReference<?>>asList(
                            new TypeReference<Address>(true) {
                            },
                            new TypeReference<Address>(true) {
                            },
                            new TypeReference<Uint256>(false) {
                            }));
            filter.addSingleTopic(EventEncoder.encode(event));
            org.web3j.protocol.core.Request<?, EthLog> ethLogRequest = web3j.ethGetLogs(filter);
            EthLog send = ethLogRequest.send();
            List<EthLog.LogResult> logs = send.getLogs();
            if (CollUtil.isNotEmpty(logs)) {
                logs.forEach(logResult -> {
                    EthLog.LogObject logObject = (EthLog.LogObject) logResult.get();
                    //				log.info("eth区块高度：{},交易记录：{}", ethBlock.getBlock().getNumber(), JSONUtil.parseObj(logObject));
                    String toReplace = StrUtil.replace(logObject.getTopics().get(2), 2, 26, "");
                    List<Account> accountList = accountService.list(new LambdaQueryWrapper<Account>()
                            .eq(Account::getMainNetwork, WalletConstant.ETH)
                    );
                    if (CollUtil.isNotEmpty(accountList)) {
                        List<String> addressList = accountList.stream().map(Account::getAddress).collect(Collectors.toList());
                        boolean contains = CollUtil.contains(addressList, toReplace);
                        if (contains) {
                            log.info("eth地址匹配成功,交易哈希：{}", logObject.getTransactionHash());

                            RechargeRecord recordServiceOne = rechargeRecordService.getOne(new LambdaQueryWrapper<RechargeRecord>()
                                    .eq(RechargeRecord::getTransactionHash, logObject.getTransactionHash())
                            );

                            if (recordServiceOne != null) {
                                log.error("eth存在相同交易哈希：{}", logObject.getTransactionHash());
                                return;
                            }

                            Account accountGet = accountList.stream().filter(account -> StrUtil.equals(account.getAddress(), toReplace)).findFirst().get();
                            RechargeRecord rechargeRecord = new RechargeRecord();
                            rechargeRecord.setId(IdUtil.getSnowflakeNextId());
                            rechargeRecord.setPublicChain(WalletConstant.ETH);
                            rechargeRecord.setAccountId(accountGet.getId());
                            rechargeRecord.setUserId(accountGet.getUserId());
                            rechargeRecord.setBlockNumber(String.valueOf(ethBlock.getBlock().getNumber()));
                            String valueStr = new BigInteger(logObject.getData().substring(2), 16).toString();
                            BigDecimal balance = new BigDecimal(valueStr).divide(wei, Integer.parseInt(currencyServiceOne.getAccuracy()), RoundingMode.DOWN);
                            rechargeRecord.setQuantity(balance);
                            rechargeRecord.setArrivalAddress(toReplace);
                            String fromReplace = StrUtil.replace(logObject.getTopics().get(1), 2, 26, "");
                            rechargeRecord.setSourceAddress(fromReplace);
                            rechargeRecord.setBlockConfirmationNumber("0");
                            rechargeRecord.setTransactionHash(logObject.getTransactionHash());
                            rechargeRecord.setState(WalletStatusEnum.RECHARGING.getStatus());
                            Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
                                    .eq(Currency::getPublicChain, WalletConstant.ETH)
                                    .eq(Currency::getState, WalletConstant.TRUE)
                                    .eq(Currency::getName, WalletConstant.USDT)
                            );
                            rechargeRecord.setBlockConfirmationRechargeNumber(currency.getConfirmationBlockRechargeNumber());
                            rechargeRecord.setMinimumRechargeNumber(currency.getMinimumRechargeAmount());
                            rechargeRecord.setCurrency(WalletConstant.USDT);
                            rechargeRecordService.save(rechargeRecord);

                            // 封装充提通知参数
                            R<User> user = remoteRippleUserService.getUserByUserId(rechargeRecord.getUserId(), SecurityConstants.FROM_IN);
                            ChargeMentionNotificationDTO notificationDTO = new ChargeMentionNotificationDTO(rechargeRecord);
                            notificationDTO.setType(UserFundRechargeTypeEnum.RECHARGE.getCode());
                            notificationDTO.setLanguage(user.getData().getLanguageCode());
                            // 用户消息通知
                            Notification notification = NotificationStruct.INSTANCE.chargeMentionDTO2PO(notificationDTO);
                            notificationService.save(notification);

                            rabbitTemplate.convertAndSend("wallet.drive.exchange", "wallet.drive.recharge.binding", rechargeRecord.getId());
                        }
                    }
                });
            }
//			log.info("----------------{}条交易记录----------------------", logs.size());

            currentBlockUpdate.setBlockNumber(String.valueOf(ethBlock.getBlock().getNumber()));
            currentBlockService.updateById(currentBlockUpdate);
        }, throwable -> {
            throwable.printStackTrace();
            throw new RuntimeException(throwable.getMessage());
        });

    }

    public void tron() {
        // 获取当前区块
        CurrentBlock currentBlock = currentBlockService.getOne(new LambdaQueryWrapper<CurrentBlock>()
                .eq(CurrentBlock::getPublicChain, WalletConstant.TRON)
        );
        Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
                // 公链
                .eq(Currency::getPublicChain, WalletConstant.TRON)
                // 状态
                .eq(Currency::getState, WalletConstant.TRUE)
                // 币种名称
                .eq(Currency::getName, WalletConstant.USDT)
        );
        if (currentBlock == null) {

            currentBlock = new CurrentBlock();
            currentBlock.setPublicChain(WalletConstant.TRON);
            currentBlock.setBlockNumber(String.valueOf(this.getLastBlockNumber(currency)));
            currentBlockService.save(currentBlock);
        }
        String blockNumber = currentBlock.getBlockNumber();
        CurrentBlock currentBlockUpdate = new CurrentBlock();
        currentBlockUpdate.setId(currentBlock.getId());

        do {
            try {
                if (this.getLastBlockNumber(currency).compareTo(new BigDecimal(blockNumber)) <= 0) {
                    TimeUnit.MILLISECONDS.sleep(ParamResolver.getLong("TRON_BLOCK_READ_TIME_INTERVAL"));
                    continue;
                }
                okhttp3.Request request = new Request.Builder()
                        .url(currency.getNodeAddress() + "v1/contracts/" +
                                currency.getSmartContractAddress() + "/events?event_name=Transfer&block_number=" +
                                blockNumber + "&only_confirmed=true&limit=200")
                        .get()
                        .addHeader("Accept", "application/json")
                        .build();
                this.tronNext(request);
                BigDecimal add = NumberUtil.add(blockNumber, "1");
                blockNumber = String.valueOf(add);
                currentBlockUpdate.setBlockNumber(String.valueOf(add));
                currentBlockService.updateById(currentBlockUpdate);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } while (true);
    }

    public BigDecimal getLastBlockNumber(Currency currency) {

        // 查询最新block
        okhttp3.Request request = new okhttp3.Request.Builder()
                .url(currency.getNodeAddress() + "walletsolidity/getnowblock")
                .get()
                .addHeader("Accept", "application/json")
                .build();

        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String res = null;
        try {
            res = response.body().string();
            String blockNumber = JSONUtil.parseObj(res).getJSONObject("block_header").getJSONObject("raw_data").getStr("number");
            return new BigDecimal(blockNumber);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }

    public void tronNext(okhttp3.Request request) {
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String res = null;
        try {
            res = response.body().string();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        TronContractsEventsTransfer tronContractsEventsTransfer = JSONUtil.toBean(res, TronContractsEventsTransfer.class);

//		log.info("tron交易记录：{}", JSONUtil.parseObj(tronContractsEventsTransfer));

        tronContractsEventsTransfer.getData().forEach(o -> {

            List<Account> accountList = accountService.list(new LambdaQueryWrapper<Account>()
                    .eq(Account::getMainNetwork, WalletConstant.TRON)
            );

            if (CollUtil.isNotEmpty(accountList)) {
                List<String> addressList = accountList.stream().map(Account::getAddress).collect(Collectors.toList());
                String toRes = o.getResult().getTo();
                String replace = StrUtil.replace(toRes, 0, 2, "41");
                ByteString bytes = ApiWrapper.parseAddress(replace);
                String tronAddress = Base58Check.bytesToBase58(bytes.toByteArray());

                if (CollUtil.contains(addressList, tronAddress)) {

                    log.info("tron地址匹配成功,交易哈希：{}", o.getTransactionId());

                    RechargeRecord recordServiceOne = rechargeRecordService.getOne(new LambdaQueryWrapper<RechargeRecord>()
                            .eq(RechargeRecord::getTransactionHash, o.getTransactionId())
                    );

                    if (recordServiceOne != null) {
                        log.error("tron存在相同交易哈希：{}", o.getTransactionId());
                        return;
                    }

                    Account accountGet = accountList.stream().filter(account -> StrUtil.equals(account.getAddress(), tronAddress)).findFirst().get();
                    RechargeRecord rechargeRecord = new RechargeRecord();
                    rechargeRecord.setId(IdUtil.getSnowflakeNextId());
                    rechargeRecord.setPublicChain(WalletConstant.TRON);
                    rechargeRecord.setAccountId(accountGet.getId());
                    rechargeRecord.setUserId(accountGet.getUserId());
                    rechargeRecord.setBlockNumber(String.valueOf(o.getBlockNumber()));
                    rechargeRecord.setTransactionHash(o.getTransactionId());

                    String fromRes = o.getResult().getFrom();
                    String fromRep = StrUtil.replace(fromRes, 0, 2, "41");
                    ByteString fromBytes = ApiWrapper.parseAddress(fromRep);
                    String fromAddress = Base58Check.bytesToBase58(fromBytes.toByteArray());

                    rechargeRecord.setSourceAddress(fromAddress);
                    rechargeRecord.setArrivalAddress(tronAddress);

                    BigDecimal bigDecimal = NumberUtil.toBigDecimal(o.getResult().getValue()).movePointLeft(6);
                    rechargeRecord.setQuantity(bigDecimal);
                    rechargeRecord.setBlockConfirmationNumber("0");
                    rechargeRecord.setState(WalletConstant.TRUE);

                    Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
                            .eq(Currency::getPublicChain, WalletConstant.TRON)
                            .eq(Currency::getState, WalletConstant.TRUE)
                            .eq(Currency::getName, WalletConstant.USDT)
                    );
                    rechargeRecord.setBlockConfirmationRechargeNumber(currency.getConfirmationBlockRechargeNumber());
                    rechargeRecord.setMinimumRechargeNumber(currency.getMinimumRechargeAmount());
                    rechargeRecord.setCurrency(WalletConstant.USDT);
                    rechargeRecordService.save(rechargeRecord);

                    // 封装充提通知参数
                    R<User> user = remoteRippleUserService.getUserByUserId(rechargeRecord.getUserId(), SecurityConstants.FROM_IN);
                    ChargeMentionNotificationDTO notificationDTO = new ChargeMentionNotificationDTO(rechargeRecord);
                    notificationDTO.setType(UserFundRechargeTypeEnum.RECHARGE.getCode());
                    notificationDTO.setLanguage(user.getData().getLanguageCode());
                    // 用户消息通知
                    Notification notification = NotificationStruct.INSTANCE.chargeMentionDTO2PO(notificationDTO);
                    notificationService.save(notification);

                    rabbitTemplate.convertAndSend("wallet.drive.exchange", "wallet.drive.recharge.binding", rechargeRecord.getId());
                }
            }
        });

        if (tronContractsEventsTransfer.getMeta().getLinks() != null) {
            okhttp3.Request requestNext = new Request.Builder()
                    .url(tronContractsEventsTransfer.getMeta().getLinks().getNext())
                    .get()
                    .addHeader("Accept", "application/json")
                    .build();
            this.tronNext(requestNext);
        }
    }

}
